//
//  PXLAppController.m
//  PXL
//
//  Created by Rachel Blackman on 8/9/07.
//  Copyright 2007 Riverdark Studios. All rights reserved.
//

#import "PXLAppController.h"
#import "PXLPhoneHelper.h"
#import "ImageBackgroundView.h"
#import "RDSourceListCell.h"
#import "RDOutlineView.h"

#include <signal.h>
#include <kvm.h>
#include <fcntl.h>
#include <sys/sysctl.h>
#include <sys/types.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>


static NSImage *s_packageImage = nil;
static NSImage *s_phoneImage = nil;
static NSImage *s_phoneLargeImage = nil;

@implementation PXLAppController

- (void) awakeFromNib
{
    _iPhoneHelper = [PXLPhoneHelper sharedPhoneHelper];
    _pxlWaitSheetEnabled = NO;
    _pxlFirmwareDownloading = NO;
    
    _pxlInstallQueue = [[NSMutableArray alloc] init];

    [_iPhoneHelper setDelegate:self];
    [_pxlPackages setDataSource:self];
    [_pxlPackages setDelegate:self];
    [(RDOutlineView *)_pxlPackages setUsesGradientSelection:YES];
    [_pxlPackages setFont:[NSFont systemFontOfSize:11.0f]];
    [_pxlPackages setFocusRingType:NSFocusRingTypeNone];
    [_pxlPackages setAutoresizesOutlineColumn:YES];
    [NSApp setDelegate:self];
    [RBSplitView class];
    [_pxlInstallLog setDataSource:self];
    _pxlInstallingPackage = nil;
    _pxlFirstInstall = NO;
    
    _pxlPackageDetailDefaultRect = [_pxlPackageDetailPanel frame];
    _pxlPackageDetailExpandedRect = NSMakeRect(_pxlPackageDetailDefaultRect.origin.x, _pxlPackageDetailDefaultRect.origin.y - _pxlPackageDetailDefaultRect.size.height, _pxlPackageDetailDefaultRect.size.width, _pxlPackageDetailDefaultRect.size.height * 2);

    if (!s_packageImage) {
        s_packageImage = [[NSImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForImageResource:@"package"]];
    }
    if (!s_phoneImage) {
        s_phoneImage = [[NSImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForImageResource:@"iPhone-small"]];
    }
    if (!s_phoneLargeImage) {
        s_phoneLargeImage = [[NSImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForImageResource:@"iPhone"]];
    }
    
    // Set up our pretty graphic foo.
    NSFont *tempFont = [NSFont systemFontOfSize:30.0f];
    [_pxlPackageTitle setFont:tempFont];
    
    [_pxlPackageTitle setStringValue:@""];
    [_pxlPackageSubtitle setStringValue:@""];
	[_pxlPackageURLView setTitle:@""];
    [_pxlPackageDescription setStringValue:@"No Package Selected."];
    [_pxlPackageRemoveButton setEnabled:NO];
    
    [_pxlPackages setBackgroundColor:[NSColor colorWithDeviceRed:0.906 green:0.930 blue:0.965 alpha:1.0]];
    [_pxlPackageCustomView setBackgroundColor:[NSColor whiteColor]];
}

- (void) dealloc
{
    [_pxlInstallQueue release];
    [super dealloc];
}

#pragma mark Menu/Action Foo

- (void) doPackageInstall:(NSString *)filename
{
    NSDictionary *pxlInfo = [_iPhoneHelper parsePxlPackage:filename];
    if (pxlInfo) {
        _pxlInstallingPackage = [pxlInfo retain];
    
        NSString *packageName = [pxlInfo objectForKey:@"RDPxlPackageName"];
        NSString *packageVersion = [pxlInfo objectForKey:@"RDPxlPackageVersion"];
        NSString *packageDescription = [pxlInfo objectForKey:@"RDPxlPackageDesc"];
        NSImage *packageIcon = [pxlInfo objectForKey:@"RDPxlPackageIconData"];
        
        if (!packageIcon) {
            packageIcon = s_packageImage;
        }
        if (!packageDescription) {
            packageDescription = @"No description available.";
        }
        
        NSString *label = [NSString stringWithFormat:@"%@ %@", packageName, packageVersion];
        [_pxlPackageDetailName setStringValue:label];
        [_pxlPackageDetailDescription setStringValue:packageDescription];
        [_pxlPackageDetailIcon setImage:packageIcon];
        
        _pxlPackageDetailExpanded = NO;
        [_pxlPackageDetailFilelist setHidden:YES];
        
        [_pxlPackageDetailFilelist setDataSource:self];

        [NSApp beginSheet:_pxlPackageDetailPanel modalForWindow:[self window] modalDelegate:self didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) contextInfo:nil];    
        _pxlPackageDetailDefaultRect = [_pxlPackageDetailPanel frame];
        _pxlPackageDetailExpandedRect = NSMakeRect(_pxlPackageDetailDefaultRect.origin.x, _pxlPackageDetailDefaultRect.origin.y - _pxlPackageDetailDefaultRect.size.height, _pxlPackageDetailDefaultRect.size.width, _pxlPackageDetailDefaultRect.size.height * 2);
    }
}

- (void) installPackage:(id) sender
{

}

- (void) removePackage:(id) sender
{
    int rowIndex = [_pxlPackages selectedRow];
    if (rowIndex != -1) {
        NSDictionary *packages = [_iPhoneHelper installedPackages];
        NSString *packageId = [_pxlPackages itemAtRow:rowIndex];
        if (packageId && ![packageId isEqualToString:@"BreezyBaseItem"])
            [_iPhoneHelper removePackage:packageId];
    }
}

- (void) detailSheetInstall:(id) sender
{
    [NSApp endSheet:_pxlPackageDetailPanel];
    [_pxlPackageDetailPanel orderOut:self];
    [_pxlPackageDetailPanel setFrame:_pxlPackageDetailDefaultRect display:NO animate:NO];
    [[_pxlPackageDetailFilelist enclosingScrollView] setHidden:YES];    
    [_pxlPackageDetailDisclosure setState:NSOffState];
    
    NSString *packagePath = [_pxlInstallingPackage objectForKey:@"RDPxlPackageBreezyPath"];    
    [_iPhoneHelper installPackage:packagePath];
    
    [_pxlInstallingPackage release];
    _pxlInstallingPackage = nil;
}

- (void) detailSheetCancel:(id) sender
{
    [NSApp endSheet:_pxlPackageDetailPanel];
    [_pxlPackageDetailPanel orderOut:self];
    [_pxlPackageDetailPanel setFrame:_pxlPackageDetailDefaultRect display:NO animate:NO];
    [[_pxlPackageDetailFilelist enclosingScrollView] setHidden:YES];    
    [_pxlPackageDetailDisclosure setState:NSOffState];
    [_pxlInstallingPackage release];
    _pxlInstallingPackage = nil;
}

- (void) detailSheetExpand:(id) sender
{
    _pxlPackageDetailExpanded = !_pxlPackageDetailExpanded;    
    if (!_pxlPackageDetailExpanded) {
        [[_pxlPackageDetailFilelist enclosingScrollView] setHidden:!_pxlPackageDetailExpanded];        
        [_pxlPackageDetailFilelist setHidden:!_pxlPackageDetailExpanded];        
    }
    [_pxlPackageDetailPanel setFrame:(_pxlPackageDetailExpanded ? _pxlPackageDetailExpandedRect : _pxlPackageDetailDefaultRect) display:YES animate:YES];
    if (_pxlPackageDetailExpanded) {
        [[_pxlPackageDetailFilelist enclosingScrollView] setHidden:!_pxlPackageDetailExpanded];        
        [_pxlPackageDetailFilelist setHidden:!_pxlPackageDetailExpanded];        
    }
    [_pxlPackageDetailFilelist setNeedsDisplay:YES];
}

- (void) application:(NSApplication *)app openFile:(NSString *)filename
{
    if ([[filename pathExtension] isEqualToString:@"pxl"]) {
        if ([_iPhoneHelper phoneState] == PhoneStatePxlEnabled) {
            [self doPackageInstall:filename];
        }
        else {
            [_pxlInstallQueue addObject:filename];
        }
    }
}

#pragma mark Process Management Foo

-(BOOL) isProcessRunning:(NSString *)processName
{
	uint32_t	    i;
    size_t			length;
	int32_t			err, count;
    struct kinfo_proc	   *process_buffer;
    struct kinfo_proc      *kp;
    int				mib[ 3 ] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL };
    int             loop, argmax;
    int             finalCount;

    finalCount = 0;

	sysctl( mib, 3, NULL, &length, NULL, 0 );

    if (length == 0)
        return -1;
	
	process_buffer = (struct kinfo_proc *)malloc(length);

    for ( i = 0; i < 60; ++i ) {
        // in the event of inordinate system load, transient sysctl() failures are
        // possible.  retry for up to one minute if necessary.
        if ( ! ( err = sysctl( mib, 3, process_buffer, &length, NULL, 0 ) ) ) break;
        sleep( 1 );
    }	

    if (err) {
        free(process_buffer);
        return -1;
    }
        
    count = length / sizeof(struct kinfo_proc);

    kp = process_buffer;


    for (loop = 0; (loop < count) && !finalCount; loop++) {
        if (!strcasecmp(kp->kp_proc.p_comm,[processName cString])) {
            finalCount++;
        }
        kp++;
    }

    free(process_buffer);
    
    return (finalCount != 0);
}

-(void) killProcess:(NSString *)processName
{
	uint32_t	    i;
    size_t			length;
	int32_t			err, count;
    struct kinfo_proc	   *process_buffer;
    struct kinfo_proc      *kp;
    int				mib[ 3 ] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL };
    int             loop, argmax;

	sysctl( mib, 3, NULL, &length, NULL, 0 );

    if (length == 0)
        return;
	
	process_buffer = (struct kinfo_proc *)malloc(length);

    for ( i = 0; i < 60; ++i ) {
        // in the event of inordinate system load, transient sysctl() failures are
        // possible.  retry for up to one minute if necessary.
        if ( ! ( err = sysctl( mib, 3, process_buffer, &length, NULL, 0 ) ) ) break;
        sleep( 1 );
    }	

    if (err) {
        free(process_buffer);
        return;
    }
        
    count = length / sizeof(struct kinfo_proc);

    kp = process_buffer;


    for (loop = 0; (loop < count); loop++) {
        if (!strcasecmp(kp->kp_proc.p_comm,[processName cString])) {
            kill(kp->kp_proc.p_pid,SIGTERM);
        }
        kp++;
    }

    free(process_buffer);
}

- (void) waitForITunesExit
{
    NSAutoreleasePool *p = [[NSAutoreleasePool alloc] init];
    while ([self isProcessRunning:@"iTunes"]) {
        sleep(3);
    }
    [self killProcess:@"iTunesHelper"];
    [_iPhoneHelper installPxl];
    [p release];
}

- (void) iTunesMustClose
{
    if ([self isProcessRunning:@"iTunes"]) {
        [_pxlSetupProgress stopAnimation:self];
        [_pxlSetupProgress setHidden:YES];
        [_pxlSetupSpinny setHidden:NO];
        [_pxlSetupSpinny setUsesThreadedAnimation:YES];
        [_pxlSetupSpinny startAnimation:self];
        [_pxlSetupCancel setEnabled:YES];
        [_pxlSetupExplanation setStringValue:@"During initial setup, iTunes cannot be running.  You must exit iTunes before Breezy will continue with the setup.\n\nOnce the PXL service is installed, iTunes and Breezy can run at the same time just fine.\n"];
        [_pxlSetupStatus setStringValue:@"Waiting for iTunes to be closed..."];
        [_pxlSetupWindow orderFront:self];
        [_pxlSetupProgress displayIfNeeded];
        
        [NSThread detachNewThreadSelector:@selector(waitForITunesExit) toTarget:self withObject:nil];
    }
    else {
        [self killProcess:@"iTunesHelper"];
        [_iPhoneHelper installPxl];
    }
}

#pragma mark DataSources

- (id)outlineView:(NSOutlineView *)outlineView child:(int)index ofItem:(id)item
{
    if (!item) {
        return @"BreezyBaseItem";
    }
    else {
        NSString *packageId = [[_iPhoneHelper installedPackageIDs] objectAtIndex:index];
        
        return packageId;
    }
}

- (int)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(id)item
{
    if (!item) {
        if ([_iPhoneHelper phoneIsConnected])
            return 1;
        else
            return 0;
    }
    else {
        return [[_iPhoneHelper installedPackages] count]; 
    }
}

- (void) outlineView:(NSOutlineView *) outlineView willDisplayCell:(id) cell forTableColumn:(NSTableColumn *) tableColumn item:(id) item 
{
    if ([(NSObject *)cell isKindOfClass:[RDSourceListCell class]]) {
        NSString *packageId = (NSString *)item;

        RDSourceListCell *slc = (RDSourceListCell *)cell;
        
        if ([packageId isEqualToString:@"BreezyBaseItem"]) {
            [slc setTitle:[_iPhoneHelper phoneName]];
        }
        else {
            NSDictionary *packages = [_iPhoneHelper installedPackages];        
            NSDictionary *pkg = [packages objectForKey:packageId];
            [slc setTitle:[pkg objectForKey:@"RDPxlPackageName"]];
        }
    }
}

- (float) outlineView:(NSOutlineView *)outlineView heightOfRowByItem:(id) item
{
    int row = [outlineView rowForItem:item];
    int level = [outlineView levelForRow:row];
    
    if (level)
        return 16.0;
    else
        return 32.0;
}

- (id) outlineView:(NSOutlineView *) outlineView objectValueForTableColumn:(NSTableColumn *) tableColumn byItem:(id) item 
{
    int row = [outlineView rowForItem:item];
    int level = [outlineView levelForRow:row];

	float maxSideSize = level ? 16.0 : 32.0;
    
    NSString *package = (NSString *)item;
    
    if ([package isEqualToString:@"BreezyBaseItem"]) {
        return s_phoneImage;
    }
    else {
        NSDictionary *packages = [_iPhoneHelper installedPackages];        
        NSDictionary *pkg = [packages objectForKey:package];
        
        NSImage *icon = [pkg objectForKey:@"RDPxlPackageIconData"];
        
        if (!icon)
            icon = s_packageImage;
        
        if( [icon size].width > maxSideSize || [icon size].height > maxSideSize ) {
            NSImage *ret = [[NSImage alloc] initWithSize:NSMakeSize(maxSideSize,maxSideSize)];
            [[NSGraphicsContext currentContext] setImageInterpolation:NSImageInterpolationHigh];
            [ret lockFocus];
            NSRect originRect = NSZeroRect;
            NSRect targetRect = NSZeroRect;
            originRect.size = [icon size];
            targetRect.size = [ret size];
            [icon drawInRect:targetRect fromRect:originRect operation:NSCompositeSourceOver fraction:1.0];
            [ret unlockFocus];
            [ret autorelease];
            icon = ret;
        }
        
        return icon;
    }

    return nil;
}

- (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(id)item
{
    if ([(NSString *)item isEqualToString:@"BreezyBaseItem"])
        return YES;
        
    return NO;
}

- (int) numberOfRowsInTableView:(NSTableView *)table
{
    if (table == _pxlInstallLog) {
        int rowIndex = [_pxlPackages selectedRow];
        if (rowIndex == -1) {
            return 0;
        }
        else {
            NSDictionary *packages = [_iPhoneHelper installedPackages];
            NSString *packageId = [_pxlPackages itemAtRow:rowIndex];
            NSDictionary *pkg = [packages objectForKey:packageId];

            if (pkg) {
                NSArray *installedFiles = [pkg objectForKey:@"RDPxlPackageInstalledFiles"];

                return [installedFiles count];
            }
            else {
                return 0;
            }
        }
    }
    else if (table == _pxlPackageDetailFilelist) {
        NSArray *files = [_pxlInstallingPackage objectForKey:@"RDPxlPackageInstallationList"];
        return [files count];
    }
}

- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(int)rowIndex
{
    if (aTableView == _pxlInstallLog) {
        int outlineRow = [_pxlPackages selectedRow];
        if (outlineRow == -1) {
            return 0;
        }
        else {
            NSDictionary *packages = [_iPhoneHelper installedPackages];
            NSString *packageId = [_pxlPackages itemAtRow:outlineRow];
            NSDictionary *pkg = [packages objectForKey:packageId];

            if (pkg) {
                NSArray *installedFiles = [pkg objectForKey:@"RDPxlPackageInstalledFiles"];

                NSDictionary *dict = [installedFiles objectAtIndex:([installedFiles count] - rowIndex - 1)];
                
                if ([[aTableColumn identifier] isEqualToString:@"text"]) {
                    NSString *filename = [dict objectForKey:@"file"];
                    
                    if ([[filename substringToIndex:2] isEqualToString:@"//"])
                        return [filename substringFromIndex:1];
                    else
                        return [dict objectForKey:@"file"];
                }
                
                return nil;
            }
            else {
                return nil;
            }
        }
    }
    else if (aTableView == _pxlPackageDetailFilelist) {
        NSArray *files = [_pxlInstallingPackage objectForKey:@"RDPxlPackageInstallationList"];
        return [files objectAtIndex:rowIndex];
    }
}

- (void)clearPackageInfo
{
    [_pxlPackageIcon setImage:nil];
    [_pxlPackageTitle setStringValue:@""];
    [_pxlPackageSubtitle setStringValue:@""];
    [_pxlPackageURLView setTitle:@""];
    [_pxlPackageDescription setStringValue:@"No Package Selected."];
    [_pxlPackageRemoveButton setEnabled:NO];    
}

- (void) displayPhoneInfo
{
    [_pxlPackageIcon setImage:s_phoneLargeImage];
    [_pxlPackageSubtitle setStringValue:[NSString stringWithFormat:@"Firmware version %@", [_iPhoneHelper phoneFirmwareDisplay]]];
    [_pxlPackageRemoveButton setEnabled:NO];
    [_pxlPackageURLView setTitle:@"http://www.apple.com/iphone/"];

    NSString *descString = [NSString stringWithFormat:@"Phone number: %@\nSerial number: %@\n\nPXL service revision %d",
                                    [_iPhoneHelper phoneNumber], [_iPhoneHelper phoneSerial], [_iPhoneHelper phonePxlRevision]];

    [_pxlPackageDescription setStringValue:descString];
    
    NSAttributedString *as;
    NSMutableDictionary *attrs = [[NSMutableDictionary alloc] init];
    
    NSFont *tempFont = [NSFont systemFontOfSize:30.0f];
    [attrs setObject:tempFont forKey:NSFontAttributeName];
    
    NSShadow* shadow = [[NSShadow alloc] init];
    [shadow setShadowColor: [NSColor darkGrayColor]];
    [shadow setShadowOffset: NSMakeSize(2, -2)];
    [shadow setShadowBlurRadius: 3.0];
    [attrs setObject: shadow
              forKey: NSShadowAttributeName];
    [shadow release];            
    [attrs setObject:[NSColor blackColor] forKey:NSForegroundColorAttributeName];

    as = [[NSAttributedString alloc] initWithString:[_iPhoneHelper phoneName] attributes:attrs];
    [_pxlPackageTitle setAttributedStringValue:as];
    [attrs release];
    [as release];
    
}

- (void) displayPackageInfo:(NSDictionary *)pkg
{
    NSString *tempString;

    tempString = [pkg objectForKey:@"RDPxlPackageName"];
    NSAttributedString *as;
    NSMutableDictionary *attrs = [[NSMutableDictionary alloc] init];
    
    NSFont *tempFont = [NSFont systemFontOfSize:30.0f];
    [attrs setObject:tempFont forKey:NSFontAttributeName];
    
    NSShadow* shadow = [[NSShadow alloc] init];
    [shadow setShadowColor: [NSColor darkGrayColor]];
    [shadow setShadowOffset: NSMakeSize(2, -2)];
    [shadow setShadowBlurRadius: 3.0];
    [attrs setObject: shadow
              forKey: NSShadowAttributeName];
    [shadow release];            
    [attrs setObject:[NSColor blackColor] forKey:NSForegroundColorAttributeName];

    as = [[NSAttributedString alloc] initWithString:tempString attributes:attrs];
    [_pxlPackageTitle setAttributedStringValue:as];
    [attrs release];
    [as release];
    
    [_pxlPackageSubtitle setStringValue:[NSString stringWithFormat:@"version %@", [pkg objectForKey:@"RDPxlPackageVersion"]]];
	
	if ([pkg objectForKey:@"RDPxlPackageURL"])
        [_pxlPackageURLView setTitle:[pkg objectForKey:@"RDPxlPackageURL"]];
	 else
        [_pxlPackageURLView setTitle:@""];

    if ([pkg objectForKey:@"RDPxlPackageDesc"])
        [_pxlPackageDescription setStringValue:[pkg objectForKey:@"RDPxlPackageDesc"]];
    else
        [_pxlPackageDescription setStringValue:@"No description available."];

    if ([pkg objectForKey:@"RDPxlPackageIconData"])
        [_pxlPackageIcon setImage:[pkg objectForKey:@"RDPxlPackageIconData"]];
    else
        [_pxlPackageIcon setImage:s_packageImage]; 

    [_pxlPackageRemoveButton setEnabled:YES];
}

- (void)outlineViewSelectionDidChange:(NSNotification *)aNotification
{
    int rowIndex = [_pxlPackages selectedRow];
    if (rowIndex == -1) {
        [self clearPackageInfo];
    }
    else {
        NSDictionary *packages = [_iPhoneHelper installedPackages];
        NSString *packageId = [_pxlPackages itemAtRow:rowIndex];
        NSDictionary *pkg = [packages objectForKey:packageId];

        if (pkg) {
            [self displayPackageInfo:pkg];
        }
        else {
            if ([packageId isEqualToString:@"BreezyBaseItem"]) {
                [self displayPhoneInfo];
            }
            else {
                [self clearPackageInfo];
            }
        }
    }
    [_pxlInstallLog reloadData];
}

- (void) clickedDetailUrl:(id) sender
{
    NSURL *url = [NSURL URLWithString:[sender title]];
    if (url)
        [[NSWorkspace sharedWorkspace] openURL:url]; 
}

#pragma mark Error Handler

- (void) errorDidOccur:(NSString *) reason
{
    [[self window] orderOut:self];
    [_pxlSetupWindow orderOut:self];
    [_pxlErrorReason setStringValue:reason];
    [_pxlErrorWindow orderFront:self];
}

#pragma mark UI Gunk

- (void)sheetDidEnd:(NSWindow *)sheet returnCode:(int)returnCode  contextInfo:(void  *)contextInfo
{
    // TODO: Uh... nothing!
}

- (void) displayPleaseWaitSheet:(NSString *)reason allowsCancel:(BOOL)canCancel
{
    [_pxlWaitPanelBar setIndeterminate:YES];
    [_pxlWaitPanelCancel setHidden:!canCancel];
    [_pxlWaitPanelLabel setStringValue:reason];
    [_pxlWaitPanelBar setUsesThreadedAnimation:YES];
    [_pxlWaitPanelBar startAnimation:self];
    if (!_pxlWaitSheetEnabled) {
        _pxlWaitSheetEnabled = YES;
        [NSApp beginSheet:_pxlWaitPanel modalForWindow:[self window] modalDelegate:self didEndSelector:@selector(sheetDidEnd:returnCode:contextInfo:) contextInfo:nil];        
    }
}

- (void) removePleaseWaitSheet
{
    if (_pxlWaitSheetEnabled) {
        _pxlWaitSheetEnabled = NO;
        [_pxlWaitPanelBar stopAnimation:self];
        [NSApp endSheet:_pxlWaitPanel];
        [_pxlWaitPanel orderOut:self];
    }
}

- (void) updatePleaseWaitSheet:(int)percentDone
{
    [_pxlWaitPanelBar setMaxValue:100];
    [_pxlWaitPanelBar setMinValue:0];
    [_pxlWaitPanelBar setDoubleValue:percentDone];
    [_pxlWaitPanelBar setIndeterminate:NO];
    [_pxlWaitPanelBar displayIfNeeded];
}

- (void) waitSheetCancel:(id) sender
{
    [self removePleaseWaitSheet];
}

#pragma mark Breezy Setup Foo

- (void) setupWindowCancel:(id) sender
{
    // TODO: Cancel firmware download, notify user that Breezy must quit.
    if (_pxlFirmwareDownloading) {
        [_pxlFirmwareDownload cancel];
        [_pxlFirmwareDownload release];
        _pxlFirmwareDownload = nil;
    }
}

- (void)download:(NSURLDownload *)download didFailWithError:(NSError *)error
{
    // release the connection
    [download release];
    [_pxlFirmwareDownloadResponse release];
    _pxlFirmwareDownloadResponse = nil;
    _pxlFirmwareDownloading = NO;
    _pxlFirmwareDownload = nil;
 
    // inform the user
    NSLog(@"Download failed! Error - %@ %@",
          [error localizedDescription],
          [[error userInfo] objectForKey:NSErrorFailingURLStringKey]);
          
    [self errorDidOccur:@"Breezy was unable to download the phone firmware from Apple, and cannot continue setup."];
}
 
- (void)downloadDidFinish:(NSURLDownload *)download
{
    // release the connection
    [download release]; 
    [_pxlFirmwareDownloadResponse release];
    _pxlFirmwareDownloadResponse = nil;
    _pxlFirmwareDownloading = NO;
    _pxlFirmwareDownload = nil;

    [self iTunesMustClose];
}

- (void)download:(NSURLDownload *)download didReceiveResponse:(NSURLResponse *)response
{
    // reset the progress, this might be called multiple times
    _pxlFirmwareBytesReceived=0;
 
    // retain the response to use later
    if (_pxlFirmwareDownloadResponse) {
        [_pxlFirmwareDownloadResponse release];
    }
    _pxlFirmwareDownloadResponse = [response retain];
}
 
- (void)download:(NSURLDownload *)download didReceiveDataOfLength:(unsigned)length
{
    long long expectedLength=[_pxlFirmwareDownloadResponse expectedContentLength];
 
    _pxlFirmwareBytesReceived=_pxlFirmwareBytesReceived+length;
 
    if (expectedLength != NSURLResponseUnknownLength) {
        float percentComplete=(_pxlFirmwareBytesReceived/(float)expectedLength)*100.0;
        [_pxlSetupProgress setDoubleValue:(double)trunc(percentComplete)];
    }
}

#pragma mark PXLPhoneHelperDelegate

- (void) phoneDidConnect:(PXLPhoneHelper *)phone
{

}

- (void) phoneDidDisconnect:(PXLPhoneHelper *)phone
{

}


- (void) phone:(PXLPhoneHelper *)phone stateDidChangeFrom:(int)oldState to:(int)newState
{
    switch (newState) {
        case PhoneStateUnknown:
            [self removePleaseWaitSheet];
            [[self window] orderOut:self];
            break;
            
        case PhoneStateJailed:
            {
                NSString *firmwareUrl = [_iPhoneHelper phoneFirmwareRestoreURL];
                NSString *firmwareFile = [[firmwareUrl componentsSeparatedByString:@"/"] lastObject];
                NSString *firmwarePath = [[NSString stringWithFormat:@"~/Library/iTunes/iPhone Software Updates/%@", firmwareFile] stringByExpandingTildeInPath];
                
                if ([[NSFileManager defaultManager] fileExistsAtPath:firmwarePath]) {
                    // We have the firmware, just go with it.
                    [self iTunesMustClose];
                }
                else {
                    [_pxlSetupSpinny setUsesThreadedAnimation:YES];
                    [_pxlSetupProgress setUsesThreadedAnimation:YES];
                    [_pxlSetupSpinny setHidden:NO];
                    [_pxlSetupSpinny startAnimation:self];
                    [_pxlSetupProgress startAnimation:self];
                    [_pxlSetupProgress setHidden:NO];
                    [_pxlSetupProgress setIndeterminate:NO];
                    [_pxlSetupCancel setEnabled:YES];
                    [_pxlSetupStatus setStringValue:@"Downloading iPhone firmware image..."];
                    [_pxlSetupExplanation setStringValue:@"We need the restore image for your iPhone in order to install the service.  Please wait while Breezy downloads the necessary file; this could take a while, but should never need to happen again."]; 
                    [_pxlSetupWindow orderFront:self];
                    _pxlFirmwareDownloading = YES;
                    
                    NSURLRequest *firmwareRequest=[NSURLRequest requestWithURL:[NSURL URLWithString:firmwareUrl]
                                                              cachePolicy:NSURLRequestUseProtocolCachePolicy
                                                          timeoutInterval:60.0];
                    NSURLDownload  *firmwareDownload=[[NSURLDownload alloc] initWithRequest:firmwareRequest
                                                                              delegate:self];
                    if (firmwareDownload) {
                        [firmwareDownload setDestination:firmwarePath allowOverwrite:NO];
                        _pxlFirmwareDownload = firmwareDownload;
                    } else {
                        [self errorDidOccur:@"Breezy was unable to download the phone firmware from Apple, and cannot continue setup."];
                    }                    
                }
            }
            break;
            
        case PhoneStateJailbroken:
        case PhoneStatePxlJailbroken:
            [_iPhoneHelper installPxl];
            _pxlFirstInstall = YES;
            break;
            
        case PhoneStatePxlEnabled:
        {
            [_pxlSetupSpinny stopAnimation:self];
            [_pxlSetupProgress stopAnimation:self];
            [_pxlSetupWindow performSelectorOnMainThread:@selector(orderOut:) withObject:self waitUntilDone:NO];
            [[self window] performSelectorOnMainThread:@selector(makeKeyAndOrderFront:) withObject:self waitUntilDone:NO]; 

            if ([_pxlInstallQueue count] && !_pxlFirstInstall) {
                NSString *newPackage = [_pxlInstallQueue objectAtIndex:0];
                [self doPackageInstall:newPackage];
                [_pxlInstallQueue removeObjectAtIndex:0];
            }

            if (_pxlFirstInstall) {
                [_iPhoneHelper installBasePackages];
                _pxlFirstInstall = NO;
            }
        } 
            break; 
    }
}

- (void) reallyRefresh
{
    int row = [_pxlPackages selectedRow];
    NSString *selected = @"BreezyBaseItem";
    if (row != -1)
        selected = [_pxlPackages itemAtRow:[_pxlPackages selectedRow]];
    [_pxlPackages reloadData];
    [_pxlPackages expandItem:@"BreezyBaseItem"];
    row = [_pxlPackages rowForItem:selected];
    if (row != -1) {
        [_pxlPackages selectRow:row byExtendingSelection:NO];
    }
    else {
        [_pxlPackages selectRow:0 byExtendingSelection:NO];
    }
    [self outlineViewSelectionDidChange:nil];
}

- (void) phoneDidRefreshPackages:(PXLPhoneHelper *)phone
{
    [self performSelectorOnMainThread:@selector(reallyRefresh) withObject:nil waitUntilDone:NO];
}

- (void) phone:(PXLPhoneHelper *)phone willInstallPackage:(NSString *)packageId
{
    NSString *label = [NSString stringWithFormat:@"Please wait, installing %@...\n\n(Installing a package can take a while, as some packages must run code on the iPhone to finish setting themselves up.)",
        packageId];
    [self displayPleaseWaitSheet:label  allowsCancel:NO];
}

- (void) phone:(PXLPhoneHelper *)phone didInstallPackage:(NSString *)packageId
{
    NSDictionary *packages = [_iPhoneHelper installedPackages];
    NSArray *keys = [packages allKeys];

    int packageNum = [keys indexOfObject:packageId];
    if (packageNum != NSNotFound) {
        [_pxlPackages selectRow:packageNum byExtendingSelection:NO];
    }
    [self removePleaseWaitSheet];
    [self performSelectorOnMainThread:@selector(reallyRefresh) withObject:nil waitUntilDone:NO];
    
    if ([_pxlInstallQueue count]) {
        NSString *newPackage = [_pxlInstallQueue objectAtIndex:0];
        [self doPackageInstall:newPackage];
        [_pxlInstallQueue removeObjectAtIndex:0];        
    }
}

- (void) phone:(PXLPhoneHelper *)phone willRemovePackage:(NSString *)packageId
{
    NSString *label = [NSString stringWithFormat:@"Please wait, removing package with ID %@...\n\n(Remove a package can take a while, as some packages must run code on the iPhone to clean themselves up.)",
        packageId];
    [self displayPleaseWaitSheet:label  allowsCancel:NO];
}

- (void) phone:(PXLPhoneHelper *)phone didRemovePackage:(NSString *)packageId
{
    [self removePleaseWaitSheet];
}

- (void) phoneWillUpdateDaemon:(PXLPhoneHelper *)phone
{
    [[self window] orderFront:self];
    NSString *label = [NSString stringWithFormat:@"Please wait, updating PXL service on %@...\n\n(This should take only a few seconds.)",
        [phone phoneName]];
    [self displayPleaseWaitSheet:label  allowsCancel:NO];
}

- (void) phoneDidUpdateDaemon:(PXLPhoneHelper *)phone
{
    [self removePleaseWaitSheet];
}

- (void) phoneIsUnpackingFirmware:(PXLPhoneHelper *)phone percentDone:(int) percentDone
{
    [_pxlSetupProgress setUsesThreadedAnimation:YES];
    [_pxlSetupProgress setIndeterminate:NO];
    [_pxlSetupProgress startAnimation:self];
    [_pxlSetupSpinny setUsesThreadedAnimation:YES];
    [_pxlSetupSpinny startAnimation:self];
    [_pxlSetupProgress setMinValue:0];
    [_pxlSetupProgress setMaxValue:100];
    [_pxlSetupProgress setDoubleValue:(double) percentDone];
    [_pxlSetupExplanation setStringValue:@"Breezy is unpacking the firmware image for your phone, to begin an appropriate installation of the PXL service."];
    [_pxlSetupStatus setStringValue:@"Unpacking firmware..."];
    [_pxlSetupProgress displayIfNeeded];
    [_pxlSetupWindow orderFront:self];
}

- (void) phoneWillJailbreak:(PXLPhoneHelper *)phone
{
    [_pxlSetupProgress stopAnimation:self];
    [_pxlSetupProgress setHidden:YES];
    [_pxlSetupSpinny setHidden:NO];
    [_pxlSetupSpinny setUsesThreadedAnimation:YES];
    [_pxlSetupSpinny startAnimation:self];
    [_pxlSetupCancel setEnabled:NO];
    [_pxlSetupStatus setStringValue:@"Waiting for phone to be placed in restore mode..."];
    [_pxlSetupExplanation setStringValue:@"Breezy is installing the PXL service onto your iPhone; this process may take several minutes, so just sit back and wait."]; 
    [_pxlSetupWindow orderFront:self];
}

- (void) phoneIsJailbreaking:(PXLPhoneHelper *)phone
{
    [_pxlSetupProgress stopAnimation:self];
    [_pxlSetupProgress setHidden:YES];
    [_pxlSetupSpinny setHidden:NO];
    [_pxlSetupSpinny setUsesThreadedAnimation:YES];
    [_pxlSetupSpinny startAnimation:self];
    [_pxlSetupCancel setEnabled:NO];
    [_pxlSetupStatus setStringValue:@"Jailbreaking phone..."];
    [_pxlSetupExplanation setStringValue:@"Breezy is installing the PXL service onto your iPhone; this process may take several minutes, so just sit back and wait."]; 
    [_pxlSetupWindow orderFront:self];
}

- (void) phoneDidJailbreak:(PXLPhoneHelper *)phone
{
    // TODO: Get rid of jailbreak notice.
}

- (void) phoneCancelledJailbreak:(PXLPhoneHelper *)phone
{
    [self errorDidOccur:@"The firmware download was cancelled, and Breezy cannot continue setup."];
}

- (void) phoneCouldNotJailbreak:(PXLPhoneHelper *)phone
{
    [self errorDidOccur:@"Breezy was unable to jailbreak the phone, and cannot continue setup."];
}


- (void) phoneWillReset:(PXLPhoneHelper *)phone forReason:(NSString *)reason
{
    [_pxlSetupProgress stopAnimation:self];
    [_pxlSetupProgress setHidden:YES];
    [_pxlSetupSpinny setHidden:NO];
    [_pxlSetupSpinny setUsesThreadedAnimation:YES];
    [_pxlSetupSpinny startAnimation:self];
    [_pxlSetupCancel setEnabled:YES];
    [_pxlSetupStatus setStringValue:@"Waiting for phone to restart..."];
    [_pxlSetupExplanation setStringValue:[NSString stringWithFormat:@"%@  In order to reset your phone, please leave it in the dock but press and hold the Home button (on the front of the phone) and the Sleep button (on the top of the phone) for about 3 seconds, then move the slider to turn the phone off.  Once the phone is off, hold the Sleep button for about 2 seconds to turn it back on.\n\nThis is the final step of the installation!", reason]]; 
    [_pxlSetupWindow orderFront:self];
    [_pxlSetupProgress displayIfNeeded];
}

- (void) phoneCouldNotInstallPxl:(PXLPhoneHelper *)phone forReason:(NSString *)reason
{
//    [self errorDidOccur:[NSString stringWithFormat:@"Breezy was unable to install the PXL service: %@\n\nThis sometimes happens when the jailbreak takes too long to finish; try restarting your phone", reason]];
    [self phoneWillReset:phone forReason:@"The jailbreak restore did not release the lockdown port yet; restarting the phone should fix this."];
}

@end
